package com.gitee.wsl.data.pool

import com.gitee.wsl.data.pool.sample.AlivePool
import com.gitee.wsl.time.useTestClock
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withTimeout

@OptIn(ExperimentalCoroutinesApi::class)
class AlivePoolTest {

    @Test
    fun lifoTest() = runTest {
        val testState = prepare(
            maxSize = 3,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.LIFO,
        )
        assertEquals(testState.lastCreatedInstanceId, 0)

        val instance1 = testState.objectPool.acquire()
        val instance2 = testState.objectPool.acquire()
        val instance3 = testState.objectPool.acquire()
//        assertFailsWith<TimeoutCancellationException> {
//                withTimeout(100) {
//                   testState.objectPool.acquire()
//            }
//        }

        assertEquals(emptyList(), testState.closed)
        assertEquals(1, instance1.identity)
        assertEquals(2, instance2.identity)
        assertEquals(3, instance3.identity)

        testState.objectPool.release(instance1)
        testState.objectPool.release(instance2)
        testState.objectPool.release(instance3)
        val instance4 = testState.objectPool.acquire()

        assertEquals(emptyList(), testState.closed)
        assertEquals(3, instance4.identity)

        delay(2.1.minutes)
        testState.objectPool.release(instance4)

        assertEquals(listOf(1, 2), testState.closed)

        testState.objectPool.close()

        advanceUntilIdle()

        assertEquals(listOf(1, 2, 3), testState.closed)
    }

    @Test
    fun fifoTest() = runTest {
        val testState = prepare(
            maxSize = 3,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.FIFO,
        )
        assertEquals(testState.lastCreatedInstanceId, 0)

        val instance1 = testState.objectPool.acquire()
        val instance2 = testState.objectPool.acquire()
        val instance3 = testState.objectPool.acquire()
        assertFailsWith<TimeoutCancellationException> {
            withTimeout(100) {
                testState.objectPool.acquire()
            }
        }

        assertEquals(emptyList(), testState.closed)
        assertEquals(1, instance1.identity)
        assertEquals(2, instance2.identity)
        assertEquals(3, instance3.identity)

        testState.objectPool.release(instance1)
        testState.objectPool.release(instance2)
        testState.objectPool.release(instance3)
        val instance4 = testState.objectPool.acquire()

        assertEquals(emptyList(), testState.closed)
        assertEquals(1, instance4.identity)

        delay(2.1.minutes)
        testState.objectPool.release(instance4)

        assertEquals(listOf(2, 3), testState.closed)

        testState.objectPool.close()

        advanceUntilIdle()

        assertEquals(listOf(2, 3, 1), testState.closed)
    }

    @Test
    fun instanceCreationCancelled() = runTest {
        val testState = prepare(
            maxSize = 1,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.LIFO,
            instanceCreationPrecondition = {
                delay(500)
            },
        )
        assertEquals(0, testState.lastCreatedInstanceId)

        val instance1Job = async {
            testState.objectPool.acquire()
        }
        delay(100)
        instance1Job.cancel()

        assertEquals(0, testState.lastCreatedInstanceId)

        val instance2Job = async {
            testState.objectPool.acquire()
        }
        delay(510)

        assertTrue(instance2Job.isCompleted)
        assertEquals(1, testState.lastCreatedInstanceId)
        assertEquals(emptyList(), testState.closed)

        testState.objectPool.close()

        advanceUntilIdle()

        assertEquals(emptyList(), testState.closed)
    }

    @Test
    fun instanceCreationFailed() = runTest {
        var alreadyFailed = false
        val testState = prepare(
            maxSize = 1,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.LIFO,
            instanceCreationPrecondition = {
                if (alreadyFailed) {
                    return@prepare
                }

                alreadyFailed = true
                throw CustomException()
            },
        )
        assertEquals(0, testState.lastCreatedInstanceId)

        assertFailsWith<CustomException> {
            testState.objectPool.acquire()
        }

        assertEquals(0, testState.lastCreatedInstanceId)

        val instance2 = testState.objectPool.acquire()

        assertEquals(1, testState.lastCreatedInstanceId)
        assertEquals(1, instance2.identity)

        assertEquals(emptyList(), testState.closed)

        testState.objectPool.close()

        advanceUntilIdle()

        assertEquals(emptyList(), testState.closed)
    }

    @Test
    fun useInstance_lifoIntegration() = runTest {
        val testState = prepare(
            maxSize = 3,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.LIFO,
        )
        assertEquals(testState.lastCreatedInstanceId, 0)

        val result1 = testState.objectPool.useInstance {
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

            delay(1)

            it?.identity
        }

        assertEquals(1, testState.lastCreatedInstanceId)
        assertEquals(1, result1)

        val result2 = testState.objectPool.useInstance {
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

            return@useInstance it?.identity
        }

        assertEquals(1, result2)
        assertEquals(1, testState.lastCreatedInstanceId)

        val result3 = testState.objectPool.useInstance {
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

            val inner = testState.objectPool.useInstance {
                assertEquals(2, testState.lastCreatedInstanceId)
                assertEquals(2, it?.identity)

                it?.identity
            }

            return@useInstance Pair(it?.identity, inner)
        }

        assertEquals(2, testState.lastCreatedInstanceId)
        assertEquals(Pair(1, 2), result3)

        assertFailsWith<CustomException> {
            testState.objectPool.useInstance {
                assertEquals(1, it?.identity)
                throw CustomException()
            }
        }
        assertEquals(2, testState.lastCreatedInstanceId)

        val result4 = testState.objectPool.useInstance {
            assertEquals(1, it?.identity)

            return@useInstance it?.identity
        }

        assertEquals(1, result4)

        testState.objectPool
            .close()

        advanceUntilIdle()

        assertEquals(listOf(2, 1), testState.closed)
    }

    @Test
    fun useInstance_fifoIntegration() = runTest {
        val testState = prepare(
            maxSize = 3,
            keepAliveFor = 2.minutes,
            strategy = AlivePool.PoolStrategy.FIFO,
        )
        assertEquals(testState.lastCreatedInstanceId, 0)

        val result1 = testState.objectPool.useInstance{
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

            delay(1)

            return@useInstance it?.identity
        }

        assertEquals(1, testState.lastCreatedInstanceId)
        assertEquals(1, result1)

        val result2 = testState.objectPool.useInstance {
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

             it?.identity
        }

        assertEquals(1, result2)
        assertEquals(1, testState.lastCreatedInstanceId)

        val result3 = testState.objectPool.useInstance {
            assertEquals(1, testState.lastCreatedInstanceId)
            assertEquals(1, it?.identity)

            val inner = testState.objectPool.useInstance {
                assertEquals(2, testState.lastCreatedInstanceId)
                assertEquals(2, it?.identity)

                it?.identity
            }

            return@useInstance Pair(it?.identity, inner)
        }

        assertEquals(2, testState.lastCreatedInstanceId)
        assertEquals(Pair(1, 2), result3)

        assertFailsWith<CustomException> {
            testState.objectPool.useInstance {
                assertEquals(2, it?.identity)
                throw CustomException()
            }
        }
        assertEquals(2, testState.lastCreatedInstanceId)

        val result4 = testState.objectPool.useInstance {
            assertEquals(1, it?.identity)

            return@useInstance it?.identity
        }

        assertEquals(1, result4)

        testState.objectPool.close()

        advanceUntilIdle()

        assertEquals(listOf(2, 1), testState.closed)
    }

    private fun TestScope.prepare(
        maxSize: Int,
        keepAliveFor: Duration?,
        strategy: AlivePool.PoolStrategy,
        instanceCreationPrecondition:  suspend () -> Unit = {},
    ): TestState {
        //val start = testTimeSource.markNow()
        var lastIdentity = 0
        val closed = mutableListOf<Int>()
        val beforeClosed = mutableListOf<Int>()
        val afterClosed = mutableListOf<Int>()
        val objectPool =  AlivePool(
            config =  AlivePool.PoolConfig(
                maxSize = maxSize,
                keepAliveFor = keepAliveFor,
                strategy = strategy,
                coroutineScope = this@prepare,
            ),
            onBeforeClose = {
                beforeClosed.add(it.identity)
            },
            onAfterClose = {
                afterClosed.add(it.identity)
            },
        ) {
            instanceCreationPrecondition()

            val currentIdentity = ++lastIdentity

            TestItem(
                identity = currentIdentity,
                closeHandler = {
                    closed.add(currentIdentity)
                },
            )
        }
        useTestClock()

//        (objectPool as  AlivePool).getTime = {
//            start.elapsedNow().inWholeMilliseconds
//        }

        assertEquals(closed, beforeClosed)
        assertEquals(closed, afterClosed)
        return TestState(
            objectPool = objectPool,
            closed = closed,
        ) {
            lastIdentity
        }
    }

    class TestState(
        val objectPool:  AlivePool<TestItem>,
        val closed: List<Int>,
        private val getLastCreatedInstanceId: () -> Int,
    ) {

        val lastCreatedInstanceId: Int
            get() = getLastCreatedInstanceId()
    }


    class TestItem(
        val identity: Int,
        private val closeHandler: () -> Unit,
    ) : AutoCloseable {

        override fun close() {
            closeHandler()
        }

        override fun equals(
            other: Any?,
        ): Boolean =
            other != null
                    && other is TestItem
                    && identity == other.identity

        override fun hashCode(): Int =
            identity.hashCode()
    }

    class CustomException : Exception()
}
